﻿/*
 * SonarAnalyzer for .NET
 * Copyright (C) 2014-2025 SonarSource Sàrl
 * mailto:info AT sonarsource DOT com
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the Sonar Source-Available License for more details.
 *
 * You should have received a copy of the Sonar Source-Available License
 * along with this program; if not, see https://sonarsource.com/license/ssal/
 */

using SonarAnalyzer.Core.Trackers;
using SonarAnalyzer.CSharp.Core.Trackers;
using SonarAnalyzer.VisualBasic.Core.Trackers;
using CS = Microsoft.CodeAnalysis.CSharp.Syntax;
using VB = Microsoft.CodeAnalysis.VisualBasic.Syntax;

namespace SonarAnalyzer.Test.Trackers;

[TestClass]
public class ObjectCreationTrackerTest
{
    [TestMethod]
    public void ConstArgumentForParameter_CS()
    {
        const string testInput = @"
public class Base
{
    private Base(string a, string b, bool c, int d, object e) {}

    private void Usage(string notAConst)
    {
      new Base(notAConst, ""myConst"", true, 4, new object());
    }
}";
        var context = CreateContext<CS.ObjectCreationExpressionSyntax>(testInput, AnalyzerLanguage.CSharp);
        var tracker = new CSharpObjectCreationTracker();

        tracker.ConstArgumentForParameter(context, "a").Should().BeNull();
        tracker.ConstArgumentForParameter(context, "b").Should().Be("myConst");
        tracker.ConstArgumentForParameter(context, "c").Should().Be(true);
        tracker.ConstArgumentForParameter(context, "d").Should().Be(4);
        tracker.ConstArgumentForParameter(context, "e").Should().BeNull();
        tracker.ConstArgumentForParameter(context, "nonExistingParameterName").Should().BeNull();
    }

#if NET
    [TestMethod]
    public void ImplicitConstArgumentForParameter_CS()
    {
        const string testInput = @"
public class Base
{
private Base(string a, string b, bool c, int d, object e) {}

private Base Usage(string notAConst)
{
  return new (notAConst, ""myConst"", true, 4, new object());
}
}";
        var context = CreateContext<CS.ImplicitObjectCreationExpressionSyntax>(testInput, AnalyzerLanguage.CSharp);
        var tracker = new CSharpObjectCreationTracker();

        tracker.ConstArgumentForParameter(context, "a").Should().BeNull();
        tracker.ConstArgumentForParameter(context, "b").Should().Be("myConst");
        tracker.ConstArgumentForParameter(context, "c").Should().Be(true);
        tracker.ConstArgumentForParameter(context, "d").Should().Be(4);
        tracker.ConstArgumentForParameter(context, "e").Should().BeNull();
        tracker.ConstArgumentForParameter(context, "nonExistingParameterName").Should().BeNull();
    }
#endif

    [TestMethod]
    public void ConstArgumentForParameter_VB()
    {
        const string testInput = @"
Public Class Base
    Sub New(ByVal a As String, b As String, ByVal c As Boolean, ByVal d As Integer, ByRef e As Integer, ByVal f As Object)
    End Sub

    Public Sub Usage(ByVal notAConst As String)
        Dim tmp = New Base(notAConst, ""myConst"", True, 4, 5, New Object())
    End Sub
End Class";
        var context = CreateContext<VB.ObjectCreationExpressionSyntax>(testInput, AnalyzerLanguage.VisualBasic);
        var tracker = new VisualBasicObjectCreationTracker();

        tracker.ConstArgumentForParameter(context, "a").Should().BeNull();
        tracker.ConstArgumentForParameter(context, "b").Should().Be("myConst");
        tracker.ConstArgumentForParameter(context, "c").Should().Be(true);
        tracker.ConstArgumentForParameter(context, "d").Should().Be(4);
        tracker.ConstArgumentForParameter(context, "e").Should().Be(5);
        tracker.ConstArgumentForParameter(context, "f").Should().BeNull();
        tracker.ConstArgumentForParameter(context, "nonExistingParameterName").Should().BeNull();
    }

    [TestMethod]
    public void ObjectCreationConditionForUndefinedSymbol()
    {
        const string testInput = @"
public class Base
{
    private void Usage(string notAConst)
    {
      new Undefined(true);
    }
}";
        var context = CreateContext<CS.ObjectCreationExpressionSyntax>(testInput, AnalyzerLanguage.CSharp);
        var tracker = new CSharpObjectCreationTracker();
        tracker.ArgumentAtIndexIs(0, KnownType.System_Boolean)(context).Should().BeFalse();
        tracker.WhenDerivesFrom(KnownType.System_Exception)(context).Should().BeFalse();
        tracker.WhenImplements(KnownType.System_IDisposable)(context).Should().BeFalse();
        tracker.WhenDerivesOrImplementsAny(KnownType.System_Boolean)(context).Should().BeFalse();
        tracker.MatchConstructor(KnownType.System_Boolean)(context).Should().BeFalse();
        tracker.ArgumentAtIndexIsConst(0).Invoke(context).Should().BeTrue();
    }

    [TestMethod]
    public void ObjectCreationConditionForNonconstructorSymbols()
    {
        const string testInput = @"
using System;

public class Base : Exception, IDisposable
{
    private void Method(bool b) { }
    public void Dispose() { }

    public void Usage()
    {
        Method(true);
    }
}";
        var context = CreateContext<CS.InvocationExpressionSyntax>(testInput, AnalyzerLanguage.CSharp);   // Created with wrong syntax
        var tracker = new CSharpObjectCreationTracker();
        tracker.ArgumentAtIndexIs(0, KnownType.System_Boolean)(context).Should().BeTrue();  // Doesn't care about symbol type
        tracker.WhenDerivesFrom(KnownType.System_Exception)(context).Should().BeFalse();
        tracker.WhenImplements(KnownType.System_IDisposable)(context).Should().BeFalse();
        tracker.WhenDerivesOrImplementsAny(KnownType.System_Exception)(context).Should().BeFalse();
        tracker.MatchConstructor(KnownType.System_Boolean)(context).Should().BeFalse();
    }

    [TestMethod]
    public void ObjectCreationNoArgumentsSupplied()
    {
        const string testInput = @"
public class Base
{
    public int Foo;
    private void Usage()
    {
      new Base { Foo = 42 };
    }
}";
        var context = CreateContext<CS.ObjectCreationExpressionSyntax>(testInput, AnalyzerLanguage.CSharp);
        var tracker = new CSharpObjectCreationTracker();
        tracker.ArgumentAtIndexIsConst(0).Invoke(context).Should().BeFalse();
    }

    [TestMethod]
    public void AndCondition()
    {
        var tracker = new CSharpObjectCreationTracker();
        CSharpObjectCreationTracker.Condition trueCondition = x => true;
        CSharpObjectCreationTracker.Condition falseCondition = x => false;

        tracker.And(trueCondition, trueCondition)(null).Should().BeTrue();
        tracker.And(trueCondition, falseCondition)(null).Should().BeFalse();
        tracker.And(falseCondition, trueCondition)(null).Should().BeFalse();
        tracker.And(falseCondition, falseCondition)(null).Should().BeFalse();
    }

    [TestMethod]
    public void OrCondition()
    {
        var tracker = new CSharpObjectCreationTracker();
        CSharpObjectCreationTracker.Condition trueCondition = x => true;
        CSharpObjectCreationTracker.Condition falseCondition = x => false;

        tracker.Or(trueCondition, trueCondition)(null).Should().BeTrue();
        tracker.Or(trueCondition, falseCondition)(null).Should().BeTrue();
        tracker.Or(falseCondition, trueCondition)(null).Should().BeTrue();
        tracker.Or(falseCondition, falseCondition)(null).Should().BeFalse();

        tracker.Or(falseCondition, falseCondition, trueCondition)(null).Should().BeTrue();
        tracker.Or(falseCondition, falseCondition, falseCondition)(null).Should().BeFalse();
    }

    private static ObjectCreationContext CreateContext<TSyntaxNodeType>(string testInput, AnalyzerLanguage language) where TSyntaxNodeType : SyntaxNode
    {
        var testCode = new SnippetCompiler(testInput, true, language);
        var node = testCode.GetNodes<TSyntaxNodeType>().First();
        var context = new ObjectCreationContext(testCode.CreateAnalysisContext(node));
        return context;
    }
}
